// TODO: CODE REVIEW & CLEANUP!
//
// MusicClientHelper.cs
//

using System;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Xml;
using HomeUX.ClientAppSupport;
using HomeUX.ClientControlSupport;
using HomeUX.Synchronization;
using SyncClientClass = HomeUX.ClientAppSupport.SyncClient;

namespace HomeUX.MusicClientLibrary
{

// note that attached screen requires ScreenTab.InitArgs="MusicDevice=<name of MusicPlayer device>"
public class MusicClientHelper
{
	const string NotAvailableMessage = "The music server is not available";

    public Screen Screen { get; private set; }
    public ISyncClient SyncClient { get; private set; }
    public string MusicDeviceName { get; private set; }
    public SyncNode MusicDeviceNode { get; private set; }
    public Guid RecipientId { get; private set; }
    public MusicClientLoadState LoadState { get; private set; }
    public string LoadingMessage { get; set; }
    Action _beginLoadingData;
    SyncNode _libraryChangeCountNode;
    List<Action> _waitingOnMusicDeviceNode = new List<Action>();

    /// <summary>
    /// Contains the value of the "LibraryChangeCount" SyncNode at the time we last loaded data
    /// from the music server.  Contains -1 if we haven't yet loaded data, or if we want to force
    /// (re)loading data regardless of the current value of LibraryChangeCount.
    /// </summary>
    int _libraryChangeCount = -1;

    public MusicClientHelper(Control screenControl, Action beginLoadingData,
        Action<Guid, XmlReader> onParcelReceived)
    {
        _beginLoadingData = beginLoadingData;
        Screen = ClientFramework.Current.TryFindScreen(screenControl);
        //Screen = ClientGlobals.ScreenControlToScreen(screenControl);

        MusicDeviceName = Screen.InitArgs.GetRequiredArgument("MusicDevice");

        SyncClient = ClientGlobals.GetSyncClient(screenControl);
        var fullSyncClient = SyncClient as SyncClientClass;
        if (fullSyncClient != null)
            RecipientId = fullSyncClient.RegisterParcelRecipient(onParcelReceived);
        MusicDeviceNode = (SyncNode)SyncClient.SyncTree.CreateNode(
            SyncPath.FromString(MusicDeviceName));
        MusicDeviceNode.ValueChanged += MusicDeviceNode_ValueChanged;
        MusicDeviceNode.IsWaitingChanged += MusicDeviceNode_IsWaitingChanged;

        _libraryChangeCountNode = (SyncNode)MusicDeviceNode.CreateNode(
            SyncPath.FromString("LibraryChangeCount"));
        _libraryChangeCountNode.ValueChanged += _libraryChangeCountNode_ValueChanged;

        LoadingMessage = "Loading...";

        if (MusicDeviceNode.Value == null)
            Screen.ShowModalMessageBox(NotAvailableMessage);
    }

    // begins the process of loading, or reloading, data from the server; if the server isn't
    // currently available the loading process will occur when it is available
    public void TryToLoadData()
    {
        LoadState = MusicClientLoadState.WaitingForServerAvailability;
        CheckLoadingState();
    }

    public void OnDataLoaded()
    {
        LoadState = MusicClientLoadState.Done;

        // update the modal message
        CheckLoadingState();
    }

    public void SetErrorState()
    {
        LoadState = MusicClientLoadState.Error;
        Screen.ShowModalMessageBox("An error occurred");
    }

    // <baseScreenName> is e.g. "Search" or "Save Queue" -- the actual screen name is constructed
    // (by convention of this library) by prepending the device name (e.g. "Music Server") followed
    // by a space -- e.g. "Music Server Save Queue"
    Screen FindMusicScreen(string baseScreenName)
    {
        string screenName = String.Format("{0} {1}", MusicDeviceName, baseScreenName);
        //Screen screen = ClientGlobals.ScreenNameToScreen(screenName);
        Screen screen = ClientFramework.Current.TryFindScreen(screenName);
        if (screen == null)
		{
            throw new ArgumentException(String.Format(
                "Screen \"{0}\" was not found",
                screenName));
		}
        return screen;
    }

    public void FindMusicScreen<T>(string baseScreenName, out Screen screen, out T screenControl)
        where T : Control
    {
        screen = FindMusicScreen(baseScreenName);
        screenControl = screen.Control as T;
        if (screenControl == null)
		{
            throw new ArgumentException(String.Format(
                "Screen \"{0}\" is not of type {1}",
				screen.Name, typeof(T).Name));
		}
    }

    public void GoToMusicScreen(string baseScreenName)
    {
        Screen screen = FindMusicScreen(baseScreenName);
        screen.GoTo();
    }

    void MusicDeviceNode_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        CheckLoadingState();
    }

    /// <summary>
    /// Calls a given action when <r>MusicDeviceNode.IsWaiting</r> is no longer <n>true</n>.
    /// If it's currently <n>false</n>, the action will be called when it's set to <n>true</n>
    /// and then <n>false</n>.
    /// </summary>
    /// <param name="action"></param>
    public void WaitForMusicDeviceNode(Action action)
    {
        _waitingOnMusicDeviceNode.Add(action);
    }

    void MusicDeviceNode_IsWaitingChanged(object sender, IsWaitingChangedEventArgs e)
    {
        if (!MusicDeviceNode.IsWaiting)
        {
            foreach (Action action in _waitingOnMusicDeviceNode)
                action();
            _waitingOnMusicDeviceNode.Clear();
        }
    }

    void _libraryChangeCountNode_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        // this method is called when the contents of the music library may have changed

        // if the contents of the music library changed since the last time we loaded data,
        // reload data from the music server
        if ((_libraryChangeCountNode.Value != null) &&
            (_libraryChangeCount != (int)_libraryChangeCountNode.Value))
        {
            _libraryChangeCount = (int)_libraryChangeCountNode.Value;
            if (LoadState == MusicClientLoadState.Done)
            {
                LoadState = MusicClientLoadState.WaitingForServerAvailability;
                CheckLoadingState();
            }
        }
    }

    void CheckLoadingState()
    {
        // if we're in an error state, continue displaying the current modal message; nothing
        // further to do
        if (LoadState == MusicClientLoadState.Error)
            return;

        if (MusicDeviceNode.Value != null)
        {
            // the music server is available...

            // if we're currently downloading data, continue displaying the current modal message;
            // nothing further to do
            if (LoadState == MusicClientLoadState.Loading)
                return;

            // if we're waiting for the music server to become available so we can download data from
            // it, and it's just become available, start downloading data
            if (LoadState == MusicClientLoadState.WaitingForServerAvailability)
            {
                _beginLoadingData();
                Screen.ShowModalMessageBox(LoadingMessage);
                LoadState = MusicClientLoadState.Loading;
                return;
            }

            // otherwise, there should be no modal message
            Screen.CloseModalMessageBox();
        }
        else
        {
            // the music server is not available
            Screen.ShowModalMessageBox(NotAvailableMessage);
        }
    }
}

public enum MusicClientLoadState
{
    /// <summary>
    /// The screen is in an error state; for example, the screen properties were not set
    /// correctly before navigating to the screen.
    /// </summary>
    Error,

    /// <summary>
    /// The screen is waiting for the music server to become available before the screen can
    /// begin loading data from the music server.
    /// </summary>
    WaitingForServerAvailability,

    /// <summary>
    /// The screen has made a request to the music server to begin loading data, and the
    /// screen is now waiting for that request to complete.
    /// </summary>
    Loading,

    /// <summary>
    /// The data has been loaded from the music server, or an error occurred during loading
    /// but we don't want to retry.
    /// </summary>
    Done
}

}
